多语言 Markdown/MDX 翻译流水线
一个关键要求是,通过确保仅发送有效的、人类可读的文本进行翻译,防止 LLM 产生幻觉或损坏代码和结构化内容。在不破坏代码、JSX、数学公式或元数据的情况下,将 Markdown 和 MDX 文件中的人类可读内容翻译,同时尽可能保留格式和结构。
核心问题
为了安全地使用 LLM 翻译文档,我们必须屏蔽或排除不可翻译的内容(如代码、JSX、数学公式和元数据)。将文档视为纯文本是不安全的,因为 LLM 可能会:
-
产生代码幻觉
-
翻译标识符
-
破坏 JSX 或 Markdown 结构
-
损坏 frontmatter 或元数据
因此,必须将文档解析为 AST(抽象语法树),以便根据节点类型选择性地应用翻译。
MDX + Docusaurus 兼容性
该项目的主要目标之一是与 Docusaurus 兼容,Docusaurus 使用 MDX(Markdown + JSX 组件)。
这给项目带来了很大的约束,因为由于需要支持 JSX、ESM 导入/导出以及嵌入的 JavaScript 表达式,在 Python 或 C++ 中还没有成熟且完全正确的 MDX 解析器。
虽然有一些 Python 库,如 tree-sitter,用于 Markdown,但它们通常不支持 MDX,并且无法安全地解析 Markdown 中的 JSX。
解决方案: 使用 unified.js 通过子进程
Docusaurus 本身使用 [unified.js](https://unifiedjs.com/),这是一个 JavaScript 生态系统,它提供了许多语言(包括 Markdown、MDX(与 JSX + ESM 配合)、GFM(表格、任务列表等)、frontmatter、Latex Math 和其他扩展)的解析、AST 转换(屏蔽)和字符串化功能。
鉴于 unified/remark 是 MDX 的行业标准解决方案,最终的设计决策是使用 JS unified.js 作为一个子进程,从 Python 中解析和转换 MD/MDX 文件。
为什么选择基于 AST 的屏蔽
这可以防止 LLM 产生幻觉和损坏代码/结构。不可翻译的内容通过排除 AST 节点(按节点类型)隐式地被屏蔽,而不是依赖启发式或正则表达式。 这样可以确保原始文档结构得以保留,同时仅翻译自然语言文本,而不会错误地将代码和 JSX 视为可翻译的文本。
虽然整个流水线可以完全在 JavaScript 中实现(并且可能更简单),但由于该项目与现有的 Python 体系集成,因此专门使用 JS 用于解析和基于 AST 的屏蔽。
架构
Python (协调器)
-
读取 Markdown/MDX 文件
-
调用 Node.js 子进程 (或长生命周期工作进程)
-
通过 stdin 发送文档文本
-
从 stdout 接收转换后的 Markdown/MDX
-
将输出写入磁盘
-
处理批量、路由和与现有 Python 系统的集成
Node.js (解析器 + AST 转换器)
-
使用 unified / remark 生态系统:
-
将 Markdown + MDX 解析为真实的 AST
-
遍历 AST
-
屏蔽 / 跳过不可翻译的节点
-
仅翻译安全的文本节点
-
将 AST 字符串化为 Markdown/MDX
-
-
确保:
-
最佳的 MDX + JSX 支持
-
正确处理 GFM 和 frontmatter
-
安全、结构感知翻译
-
JS 堆栈
-
核心堆栈 (Node.js)
-
unified
-
remark-parse
-
remark-mdx
-
remark-gfm
-
remark-frontmatter
-
remark-stringify
-
unist-util-visit-parents
-
LLM 后端 (例如,Ollama)
替代方案 (未选择)
| 选项 | 原因 |
|---|---|
| Tree-sitter | 适用于多语言解析,但对 MDX 和 JSX 敏感的 Markdown 的打印和回程支持不足 |
| Pandoc/Panflute | 适用于 Markdown,但对 MDX + JSX 较弱 |
| 纯 Python 解析器 | 没有真实的 MDX 支持 |
| 正则表达式 / 文本启发式 | 不安全;会导致幻觉和文档损坏 |